#pragma once

#include <math.h>

#define V3D      Vector3D<TYPE>
#define V3DI     Vector3D<int>
#define V3DF     Vector3D<float>
#define V3D_ZERO V3DF(0,0,0)
#define V3D_ONES V3DF(1,1,1)
#define V3D_DOUB V3DF(2,2,2)
#define V3D_X    V3DF(1,0,0)
#define V3D_Y    V3DF(0,1,0)
#define V3D_Z    V3DF(0,0,1)
#define V3D_XY   V3DF(1,1,0)
#define V3D_XZ   V3DF(1,0,1)
#define V3D_YZ   V3DF(0,1,1)

template <typename TYPE>
struct Vector3D
{
	TYPE x, y, z;

	Vector3D(TYPE a_x = 0, TYPE a_y = 0, TYPE a_z = 0)
	{x=a_x; y=a_y; z=a_z;}
	Vector3D(Vector3D<TYPE>& v)
	{x=v.x; y=v.y; z=v.z;}

	//operator overload
	TYPE operator[](const int& n)
	{return (n==0)?x:(n==1)?y:(n==2)?z:0;}

	V3D operator+(const V3DF& n)
	{return Vector3D(x+n.x,y+n.y,z+n.z);}
	V3D operator-(const V3DF& n)
	{return Vector3D(x-n.x,y-n.y,z-n.z);}
	V3D operator*(const V3DF& n)
	{return Vector3D(x*n.x,y*n.y,z*n.z);}
	V3D operator/(const V3DF& n)
	{return Vector3D(x/n.x,y/n.y,z/n.z);}

	void operator+=(const V3D& v)
	{x += v.x; y += v.y; z += v.z;}
	void operator-=(const V3D& v)
	{x -= v.x; y -= v.y; z -= v.z;}
	void operator*=(const V3D& v)
	{x *= v.x; y *= v.y; z *= v.z;}
	void operator/=(const V3D& v)
	{x /= v.x; y /= v.y; z /= v.z;}

	V3D operator+(const TYPE& n)
	{return Vector3D(x+n,y+n,z+n);}
	V3D operator-(const TYPE& n)
	{return Vector3D(x-n,y-n,z-n);}
	V3D operator*(const TYPE& n)
	{return Vector3D(x*n,y*n,z*n);}
	V3D operator/(const TYPE& n)
	{return Vector3D(x/n,y/n,z/n);}

	void operator+=(const TYPE& n)
	{x += n; y += n; z += n;}
	void operator-=(const TYPE& n)
	{x -= n; y -= n; z -= n;}
	void operator*=(const TYPE& n)
	{x *= n; y *= n; z *= n;}
	void operator/=(const TYPE& n)
	{x /= n; y /= n; z /= n;}

	bool operator<(const V3DF& v)
	{return (x < v.x && y < v.y && z < v.z);}
	bool operator>(const V3DF& v)
	{return (x > v.x && y > v.y && z > v.z);}
	bool operator<=(const V3DF& v)
	{return (x <= v.x && y <= v.y && z <= v.z);}
	bool operator>=(const V3DF& v)
	{return (x >= v.x && y >= v.y && z >= v.z);}

	bool operator==(const V3DF& v)
	{return (x == v.x && y == v.y && z == v.z);}
	bool operator!=(const V3DF& v)
	{return (x != v.x && y != v.y && z != v.z);}

	//setter functions
	void set(TYPE nx, TYPE ny, TYPE nz)
	{x=nx;y=ny;z=nz;}

	void set(V3D v)
	{x=v.x;y=v.y;z=v.z;}

	void limit(TYPE n)
	{
		if(magnitude() > n)
			*this = normal()*n;
	}

	//bool functions
	bool isZero()
	{return (x == 0 && y == 0 && z == 0);}

	//void functions
	void zero()
	{x = 0; y = 0; z = 0;}

	void negate()
	{x = -x; y = -y; z = -z;}

	//class functions
	V3D normalize()
	{
		float mag = magnitude();
		if(x != 0)
			x /= mag;
		if(y != 0)
			y /= mag;
		if(z != 0)
			z /= mag;
		return *this;
	}

	V3D normal()
	{V3DF norm(*this); return norm.normalize();}

	V3D perp(){return V3DF(-y,x,z);}

	//scalar functions
	TYPE magnitudeSq()
	{return x*x+y*y+z*z;}

	TYPE magnitude()
	{return sqrt(magnitudeSq());}

	TYPE distanceSq(V3DF v)
	{TYPE dx = x-v.x, dy = y-v.y, dz = z-v.z; return dx*dx+dy*dy+dz*dz;}

	TYPE distance(V3DF v)
	{return sqrt(distanceSq(v));}

	TYPE dot(V3DF v)
	{return (x*v.x + y*v.y + z*v.z);}

	TYPE minValue()
	{return (x<y&&x<z)?x:(y<z)?y:z;}

	TYPE maxValue()
	{return (x>y&&x>z)?x:(y>z)?y:z;}
};